use super::*;

/// A [Device Table](
/// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#devVarIdxTbls).
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum Device<'a> {
	Hinting(HintingDevice<'a>),
	Variation(VariationDevice),
}
impl<'a> FromSlice<'a> for Device<'a> {
	fn parse(data: &'a [u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		let first = s.read::<u16>()?;
		let second = s.read::<u16>()?;
		let format = s.read::<u16>()?;
		match format {
			1..=3 => {
				let start_size = first;
				let end_size = second;
				let count = (1 + (end_size - start_size)) >> (4 - format);
				let delta_values = s.read_array16(count)?;
				Some(Self::Hinting(HintingDevice {
					start_size,
					end_size,
					delta_format: format,
					delta_values,
				}))
			}
			0x8000 => Some(Self::Variation(VariationDevice {
				outer_index: first,
				inner_index: second,
			})),
			_ => None,
		}
	}
}

/// A [Device Table](https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#devVarIdxTbls)
/// indexes into [Item Variation Store](
/// https://docs.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#IVS).
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub struct VariationDevice {
	pub outer_index: u16,
	pub inner_index: u16,
}

/// A [Device Table](
/// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#devVarIdxTbls)
/// hinting values.
#[derive(Clone, Copy)]
pub struct HintingDevice<'a> {
	start_size: u16,
	end_size: u16,
	delta_format: u16,
	delta_values: LazyArray16<'a, u16>,
}
impl HintingDevice<'_> {
	/// Returns X-axis delta.
	pub fn x_delta(&self, units_per_em: u16, pixels_per_em: Option<(u16, u16)>) -> Option<i32> {
		let ppem = pixels_per_em.map(|(x, _)| x)?;
		self.get_delta(ppem, units_per_em)
	}

	/// Returns Y-axis delta.
	pub fn y_delta(&self, units_per_em: u16, pixels_per_em: Option<(u16, u16)>) -> Option<i32> {
		let ppem = pixels_per_em.map(|(_, y)| y)?;
		self.get_delta(ppem, units_per_em)
	}

	fn get_delta(&self, ppem: u16, scale: u16) -> Option<i32> {
		let f = self.delta_format;
		debug_assert!(matches!(f, 1..=3));

		if ppem == 0 || ppem < self.start_size || ppem > self.end_size {
			return None;
		}

		let s = ppem - self.start_size;
		let byte = self.delta_values.get(s >> (4 - f))?;
		let bits = byte >> (16 - (((s & ((1 << (4 - f)) - 1)) + 1) << f));
		let mask = 0xFFFF >> (16 - (1 << f));

		let mut delta = i64::from(bits & mask);
		if delta >= i64::from((mask + 1) >> 1) {
			delta -= i64::from(mask + 1);
		}

		i32::try_from(delta * i64::from(scale) / i64::from(ppem)).ok()
	}
}
impl core::fmt::Debug for HintingDevice<'_> {
	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
		write!(f, "HintingDevice {{ ... }}")
	}
}
